package com.oss.util.easyexcel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 读取excel监听器
 * 此类不能被spring管理，要每次读取excel都要new
 * @author chegnqiang
 * @param <T> 形参
 */
@Data
@Slf4j
public class EasyExcelDataListener<T> extends AnalysisEventListener<T> {
    /**
     * 不需要校验的表头行下标
     * 部分模板中表头是多行的 , 某一行中的数据是动态的 , 不需要校验
     * 比如表头中存在日期之类的 , 模板中的都是固定的 , 但导入的日期是不确定的 , 无法检验
     */
    List<Integer> unNeedCheckTableHeaderRowIndexList = new ArrayList<>();
    // 表头行数
    private int tableHeaderRows = 0;
    // 合并单元格信息
    List<CellExtra> cellExtras = new ArrayList<>();
    // excel数据
    List<T> rows = new ArrayList<>();
    // 导入的excel附件中的表头(导入文件中的表头)
    private List<ExcelHead> importHeadList = new ArrayList<>();
    // 用来校验的表头, 调用处传入(模板文件中的表头)
    private List<ExcelHead> templateHeadList = new ArrayList<>();
    // 是否进行下一步的标识
    public boolean checkPassFlag = true;
    // 不进行下一步的原因(错误信息)
    public String errorMessage;
    // 是否忽略空白行(不读取空白行) 适用于批量读取sheet(单个读取也不是不可以用)
    public Boolean ignoreEmptyRow = null;
    /**
     * 必须读取并返回的行下标列表(针对目标数据在表头中)
     * 例如 : 某一行是表头 , 但是需要其中某一列的数据
     */
    private List<Integer> mustBeReadRowIndex = new ArrayList<>();
    /**
     * 必须读取的行 返回值(针对目标数据在表头中)
     * key = 行号 , value = 行户数据(key=列号,value=单元格值)
     */
    private Map<Integer, Map<Integer, String>> mustBeReadRowData = new HashMap<>();
    /**
     * 是否校验表头中的空白行(如果存在抛出异常)
     */
    private boolean checkTableHeaderEmptyRow = false;

    @Override
    public void invoke(T t, AnalysisContext analysisContext) {
        /**
         * 如果没有设置值 , 取读取excel附件处设置的值(不设置默认为true)
         * 这样设置就不会和本类中的ignoreEmptyRow变量冲突了
         */
        if (ignoreEmptyRow == null) {
            ignoreEmptyRow = analysisContext.readWorkbookHolder().getIgnoreEmptyRow();
            analysisContext.readWorkbookHolder().setIgnoreEmptyRow(ignoreEmptyRow);
        } else {
            analysisContext.readWorkbookHolder().setIgnoreEmptyRow(ignoreEmptyRow);
        }
        rows.add(t);
    }
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
    }
    // 优先读取表头
    @Override
    public void invokeHeadMap(Map<Integer, String> importHeaderMap, AnalysisContext context) {
        ReadRowHolder readRowHolder = context.readRowHolder();
        // 行号(多级表头)
        int rowIndex = readRowHolder.getRowIndex();
        ExcelHead importExcelHead = new ExcelHead();
        importExcelHead.setRowIndex(rowIndex);
        importExcelHead.setMap(importHeaderMap);
        importHeadList.add(importExcelHead);
        if (mustBeReadRowIndex.contains(rowIndex)) {
            mustBeReadRowData.put(rowIndex,importHeaderMap);
        }
        // 校验表头是否存在空白行
        if (checkTableHeaderEmptyRow == true) {
            if (CollectionUtils.isEmpty(importHeaderMap)){
                checkPassFlag = false;
                errorMessage = "导入文件的表头存在空行 , 行号="+(rowIndex+1);
            }
        }
        // 当前行需要校验表头 , 才进入校验表头行
        if (!unNeedCheckTableHeaderRowIndexList.contains(rowIndex)) {
            try {
                checkExcelHeader(importHeaderMap,rowIndex);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 校验导入的excel中的表头是否符合预期
     * @param importHeaderMap 导入的文件表头
     * @param rowIndexItem 行号
     */
    public void checkExcelHeader(Map<Integer,String> importHeaderMap,Integer rowIndexItem) throws Exception {
        if (CollectionUtils.isNotEmpty(templateHeadList)) {

            // 一行只有一个表头, 此list有且只有一条数据,否则就是表头不正确
            List<ExcelHead> templateList = templateHeadList.stream().filter(item->item.getRowIndex()==rowIndexItem).collect(Collectors.toList());
            if (templateList.size() != 1 ){
                checkPassFlag = false;
                errorMessage = "导入文件的表头不正确,请按照模板格式编写";
            } else {
                ExcelHead templateHead = templateList.get(0);
                Map<Integer,String> templateHeadMap = templateHead.getMap();
                if (templateHeadMap != null && templateHeadMap.size() != importHeaderMap.size()){
                    checkPassFlag = false;
                    errorMessage = "导入文件的表头不正确 [表头和模板不一致] 请按照模板格式编写";
                }
                for (Map.Entry<Integer,String> entry : importHeaderMap.entrySet()) {
                    Integer columnIndex = entry.getKey();
                    String columnValue = entry.getValue();
                    columnValue = columnValue == null ? null : columnValue.trim();
                    if (templateHeadMap != null) {
                        String templateValue = templateHeadMap.get(columnIndex);
                        templateValue = templateValue == null ? null : templateValue.trim();
                        if (columnValue != null && templateValue != null && !columnValue.equals(templateHeadMap.get(columnIndex))){
                            checkPassFlag = false;
                            errorMessage = "导入文件的表头不正确, 请按照模板格式编写";
                        }
                    }
                }
            }
        }
    }

    /**
     * 是否进行下一步
     * @param context
     * @return 返回false不进行 , 反之进行
     */
    @Override
    public boolean hasNext(AnalysisContext context) {
        return checkPassFlag;
    }

    /**
     * 处理合并元格中, 数据读不到返回null问题
     * @param extra
     * @param context
     */
    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        if (extra.getFirstRowIndex() < tableHeaderRows) {
            return ;
        }
        CellExtraTypeEnum type = extra.getType();
        if (type == CellExtraTypeEnum.MERGE){
            cellExtras.add(extra);
        }
    }
    // 去除首位空格
    public void trimFields(Object obj) throws Exception {
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            if (field.getType().equals(String.class)) {
                field.setAccessible(true);
                String typeName = field.getType().getName();
                String value = (String) field.get(obj);
                log.info("字段类型="+typeName+" ; 字段值="+value);
//                if (value != null) {
//                    value = value.trim();
//                    field.set(obj, value);
//                }
            }
        }
    }
}
